/*
 * Copyright 2013-2017 Keisuke Kobayashi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.kobakei.ratethisapp;

import com.kobakei.utils.BrowserIntents;
import com.kobakei.utils.PreferencesUtil;
import com.kobakei.utils.ScreenUtils;
import com.kobakei.utils.TextUtils;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.window.dialog.CommonDialog;
import ohos.app.Context;
import ohos.data.preferences.Preferences;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.net.Uri;

import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * RateThisApp<br>
 * A library to show the app rate dialog
 *
 * @author Keisuke Kobayashi (k.kobayashi.122@gmail.com)
 */
public class RateThisApp {
    /**
     * If true, print LogCat
     */
    public static final boolean DEBUG = true;

    private static final String TAG = RateThisApp.class.getSimpleName();

    static final HiLogLabel LOG_LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG);

    private static final String PREF_NAME = "RateThisApp";
    private static final String KEY_INSTALL_DATE = "rta_install_date";
    private static final String KEY_LAUNCH_TIMES = "rta_launch_times";
    private static final String KEY_OPT_OUT = "rta_opt_out";
    private static final String KEY_ASK_LATER_DATE = "rta_ask_later_date";

    private static Date mInstallDate = new Date();
    private static int mLaunchTimes = 0;
    private static boolean mOptOut = false;
    private static Date mAskLaterDate = new Date();

    private static Config sConfig = new Config();
    private static Callback sCallback = null;

    // Weak ref to avoid leaking the context
    private static WeakReference<CommonDialog> sDialogRef = null;

    /**
     * Initialize RateThisApp configuration.
     *
     * @param config Configuration object.
     */
    public static void init(Config config) {
        sConfig = config;
    }

    /**
     * Set callback instance.
     * The callback will receive yes/no/later events.
     *
     * @param callback
     */
    public static void setCallback(Callback callback) {
        sCallback = callback;
    }

    /**
     * Call this API when the launcher ability is launched.<br>
     *
     * It is better to call this API in onCreate() of the launcher ability.
     *
     * @param context Context
     */
    public static void onCreate(Context context) {
        Preferences pref = PreferencesUtil.getInstance(context);

        // If it is the first launch, save the date in shared preference.
        // Increment launch times
        if (pref.getLong(KEY_INSTALL_DATE, 0) == 0L) {
            storeInstallDate(context, pref);
        }
        int launchTimes = pref.getInt(KEY_LAUNCH_TIMES, 0);
        launchTimes++;
        pref.putInt(KEY_LAUNCH_TIMES, launchTimes);
        log("Launch times; " + launchTimes);

        mInstallDate = new Date(pref.getLong(KEY_INSTALL_DATE, 0));
        mLaunchTimes = pref.getInt(KEY_LAUNCH_TIMES, 0);
        mOptOut = pref.getBoolean(KEY_OPT_OUT, false);
        mAskLaterDate = new Date(pref.getLong(KEY_ASK_LATER_DATE, 0));

        printStatus(context);
    }

    /**
     * This API is deprecated.
     * You should call onCreate instead of this API in ability's onCreate().
     *
     * @param context
     */
    @Deprecated
    public static void onStart(Context context) {
        onCreate(context);
    }

    /**
     * Show the rate dialog if the criteria is satisfied.
     *
     * @param context Context
     * @return true if shown, false otherwise.
     */
    public static boolean showRateDialogIfNeeded(final Context context) {
        if (shouldShowRateDialog()) {
            showRateCommonDialog(context);
            return true;
        } else {
            return false;
        }
    }

    /**
     * Show the rate dialog if the criteria is satisfied.
     *
     * @param context Context
     * @param colors Theme ID
     * @return true if shown, false otherwise.
     */
    public static boolean showRateDialogIfNeeded(final Context context, RgbColor... colors) {
        if (shouldShowRateDialog()) {
            showRateCommonDialog(context, colors);
            return true;
        } else {
            return false;
        }
    }

    /**
     * Check whether the rate dialog should be shown or not.
     * Developers may call this method directly if they want to show their own view instead of
     * dialog provided by this library.
     *
     * @return boolean
     */
    public static boolean shouldShowRateDialog() {
        if (mOptOut) {
            return false;
        } else {
            if (mLaunchTimes >= sConfig.mCriteriaLaunchTimes) {
                return true;
            }
            long threshold = TimeUnit.DAYS.toMillis(sConfig.mCriteriaInstallDays);
            if (new Date().getTime() - mInstallDate.getTime() >= threshold
                    && new Date().getTime() - mAskLaterDate.getTime() >= threshold) {
                return true;
            }
            return false;
        }
    }

    /**
     * Show the rate dialog
     *
     * @param context
     * @param colors colors
     */
    public static void showRateCommonDialog(final Context context,RgbColor... colors) {
        showRateDialog(context,colors);
    }

    /**
     * Stop showing the rate dialog
     *
     * @param context
     */
    public static void stopRateDialog(final Context context) {
        setOptOut(context, true);
    }

    /**
     * Get count number of the rate dialog launches
     *
     * @param context
     * @return int
     */
    public static int getLaunchCount(final Context context) {
        Preferences pref = PreferencesUtil.getInstance(context);
        return pref.getInt(KEY_LAUNCH_TIMES, 0);
    }

    /**
     * showRateDialog
     *
     * @param context context
     * @param colors 设定色彩style  顺序分别为 按键色，文字颜色，背景色
     */
    private static void showRateDialog(Context context,RgbColor... colors) {
        if (sDialogRef != null && sDialogRef.get() != null) {
            return;
        }

        int titleId = sConfig.mTitleId != 0 ? sConfig.mTitleId : ResourceTable.String_rta_dialog_title;
        int messageId = sConfig.mMessageId != 0 ? sConfig.mMessageId : ResourceTable.String_rta_dialog_message;
        int cancelButtonID = sConfig.mCancelButton != 0
                ? sConfig.mCancelButton : ResourceTable.String_rta_dialog_cancel;
        int thanksButtonID = sConfig.mNoButtonId != 0 ? sConfig.mNoButtonId : ResourceTable.String_rta_dialog_no;
        int rateButtonID = sConfig.mYesButtonId != 0 ? sConfig.mYesButtonId : ResourceTable.String_rta_dialog_ok;

        ComponentContainer customToastLayout = (ComponentContainer) LayoutScatter
                .getInstance(context).parse(ResourceTable.Layout_dialog_layout, null, false);

        Text mTitle = (Text) customToastLayout.findComponentById(ResourceTable.Id_tv_title);
        Text mContent = (Text) customToastLayout.findComponentById(ResourceTable.Id_tv_content);
        Text mBtCancel = (Text) customToastLayout.findComponentById(ResourceTable.Id_tv_cancel);
        Text mBtNo = (Text) customToastLayout.findComponentById(ResourceTable.Id_tv_no);
        Text mBtConfim = (Text) customToastLayout.findComponentById(ResourceTable.Id_tv_confirm);

        mTitle.setText(context.getString(titleId));
        mContent.setText(context.getString(messageId));
        mBtCancel.setText(context.getString(cancelButtonID));
        mBtNo.setText(context.getString(thanksButtonID));
        mBtConfim.setText(context.getString(rateButtonID));

        mBtNo.setTextColor(new Color(Color.rgb(colors[0].getRed(),colors[0].getGreen(),colors[0].getBlue())));
        mBtConfim.setTextColor(new Color(Color.rgb(colors[0].getRed(),colors[0].getGreen(),colors[0].getBlue())));
        mBtCancel.setTextColor(new Color(Color.rgb(colors[0].getRed(),colors[0].getGreen(),colors[0].getBlue())));

        mTitle.setTextColor(new Color(Color.rgb(colors[1].getRed(),colors[1].getGreen(),colors[1].getBlue())));
        mContent.setTextColor(new Color(Color.rgb(colors[1].getRed(),colors[1].getGreen(),colors[1].getBlue())));

        ComponentContainer.LayoutConfig layoutConfig = new ComponentContainer.LayoutConfig(
                ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_CONTENT);
        ShapeElement element = new ShapeElement();
        element.setRgbColor(colors[2]);
        customToastLayout.setBackground(element);
        customToastLayout.setLayoutConfig(layoutConfig);

        CommonDialog commonDialog = new CommonDialog(context);
        commonDialog.setContentCustomComponent(customToastLayout);
        commonDialog.setAutoClosable(false);
        commonDialog.setSize(ScreenUtils.getDisplayWidth(context) / 7 * 6, DirectionalLayout.LayoutConfig.MATCH_CONTENT);
        commonDialog.setAlignment(LayoutAlignment.CENTER);
        initViews(context, mBtCancel, mBtNo, mBtConfim, commonDialog);
    }

    private static void initViews(Context context, Text mBtCancel, Text mBtNo, Text mBtConfim, CommonDialog commonDialog) {
        switch (sConfig.mCancelMode) {
            case Config.CANCEL_MODE_BACK_KEY_OR_TOUCH_OUTSIDE:
                commonDialog.setAutoClosable(true); // It's the default anyway
                break;
            case Config.CANCEL_MODE_BACK_KEY:
                commonDialog.setAutoClosable(false);
                break;
            case Config.CANCEL_MODE_NONE:
                commonDialog.setAutoClosable(false);
                break;
            default:
                break;
        }
        mBtConfim.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                confim(context, commonDialog);
            }
        });
        mBtCancel.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                cancel(context, commonDialog);
            }
        });
        mBtNo.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                noConfim(context, commonDialog);
            }
        });
        commonDialog.setDestroyedListener(new CommonDialog.DestroyedListener() {
            @Override
            public void onDestroy() {
                destroy();
            }
        });
        commonDialog.show();
        sDialogRef = new WeakReference<>(commonDialog);
    }

    private static void destroy() {
        sDialogRef.clear();
        sCallback.onCancelClicked();
    }

    private static void noConfim(Context context, CommonDialog commonDialog) {
        if (sCallback != null) {
            sCallback.onNoClicked();
        }
        clearSharedPreferences(context);
        storeAskLaterDate(context);
        sDialogRef.clear();
        commonDialog.hide();
    }

    private static void cancel(Context context, CommonDialog commonDialog) {
        if (sCallback != null) {
            sCallback.onCancelClicked();
        }
        clearSharedPreferences(context);
        storeAskLaterDate(context);
        sDialogRef.clear();
        commonDialog.hide();
    }

    private static void confim(Context context, CommonDialog commonDialog) {
        if (sCallback != null) {
            sCallback.onYesClicked();
        }
        String appPackage = context.getBundleName();
        String url = "market://details?id=" + appPackage;
        if (!TextUtils.isEmpty(sConfig.mUrl)) {
            url = sConfig.mUrl;
        }
        try {
            loadUrl(Uri.parse(url), context);
        } catch (NoClassDefFoundError e) {
            String error = e.toString();
        }
        setOptOut(context, true);
        sDialogRef.clear();
        commonDialog.hide();
    }

    /**
     * Clear data in shared preferences.<br>
     * This API is called when the "Later" is pressed or canceled.
     *
     * @param context
     */
    private static void clearSharedPreferences(Context context) {
        Preferences pref = PreferencesUtil.getInstance(context);
        pref.delete(KEY_INSTALL_DATE);
        pref.delete(KEY_LAUNCH_TIMES);
    }

    /**
     * Set opt out flag.
     * If it is true, the rate dialog will never shown unless app data is cleared.
     * This method is called when Yes or No is pressed.
     *
     * @param context
     * @param optOut
     */
    private static void setOptOut(final Context context, boolean optOut) {
        Preferences pref = PreferencesUtil.getInstance(context);
        pref.putBoolean(KEY_OPT_OUT, optOut);
        mOptOut = optOut;
    }

    /**
     * Store install date.
     * Install date is retrieved from package manager if possible.
     *
     * @param context
     * @param preferences
     */
    private static void storeInstallDate(final Context context, Preferences preferences) {
        Date installDate = new Date();
        preferences.putLong(KEY_INSTALL_DATE, installDate.getTime());
        log("First install: " + installDate.toString());
    }

    /**
     * Store the date the user asked for being asked again later.
     *
     * @param context
     */
    private static void storeAskLaterDate(final Context context) {
        Preferences pref = PreferencesUtil.getInstance(context);
        pref.putLong(KEY_ASK_LATER_DATE, System.currentTimeMillis());
    }

    /**
     * Print values in SharedPreferences (used for debug)
     *
     * @param context
     */
    private static void printStatus(final Context context) {
        Preferences pref = PreferencesUtil.getInstance(context);
        log("*** RateThisApp Status ***");
        log("Install Date: " + new Date(pref.getLong(KEY_INSTALL_DATE, 0)));
        log("Launch Times: " + pref.getInt(KEY_LAUNCH_TIMES, 0));
        log("Opt out: " + pref.getBoolean(KEY_OPT_OUT, false));
    }

    /**
     * Print log if enabled
     *
     * @param message
     */
    private static void log(String message) {
        if (DEBUG) {
            HiLog.debug(LOG_LABEL, message);
        }
    }

    /**
     * RateThisApp configuration.
     *
     * @since 2021-05-25
     */
    public static class Config {
        /**
         * CANCEL_MODE_BACK_KEY_OR_TOUCH_OUTSIDE
         */
        public static final int CANCEL_MODE_BACK_KEY_OR_TOUCH_OUTSIDE = 0;
        /**
         * CANCEL_MODE_BACK_KEY
         */
        public static final int CANCEL_MODE_BACK_KEY = 1;
        /**
         * CANCEL_MODE_NONE
         */
        public static final int CANCEL_MODE_NONE = 2;

        private String mUrl = null;
        private int mCriteriaInstallDays;
        private int mCriteriaLaunchTimes;
        private int mTitleId = 0;
        private int mMessageId = 0;
        private int mYesButtonId = 0;
        private int mNoButtonId = 0;
        private int mCancelButton = 0;
        private int mCancelMode = CANCEL_MODE_BACK_KEY_OR_TOUCH_OUTSIDE;

        /**
         * Constructor with default criteria.
         */
        public Config() {
            this(7, 10);
        }

        /**
         * Constructor.
         *
         * @param criteriaInstallDays
         * @param criteriaLaunchTimes
         */
        public Config(int criteriaInstallDays, int criteriaLaunchTimes) {
            this.mCriteriaInstallDays = criteriaInstallDays;
            this.mCriteriaLaunchTimes = criteriaLaunchTimes;
        }

        /**
         * Set title string ID.
         *
         * @param stringId
         */
        public void setTitle(int stringId) {
            this.mTitleId = stringId;
        }

        /**
         * Set message string ID.
         *
         * @param stringId
         */
        public void setMessage(int stringId) {
            this.mMessageId = stringId;
        }

        /**
         * Set rate now string ID.
         *
         * @param stringId
         */
        public void setYesButtonText(int stringId) {
            this.mYesButtonId = stringId;
        }

        /**
         * Set no thanks string ID.
         *
         * @param stringId
         */
        public void setNoButtonText(int stringId) {
            this.mNoButtonId = stringId;
        }

        /**
         * Set cancel string ID.
         *
         * @param stringId
         */
        public void setCancelButtonText(int stringId) {
            this.mCancelButton = stringId;
        }

        /**
         * Set navigation url when user clicks rate button.
         *
         * @param url
         */
        public void setUrl(String url) {
            this.mUrl = url;
        }

        /**
         * Set the cancel mode; namely, which ways the user can cancel the dialog.
         *
         * @param cancelMode
         * @since 2021-05-25
         */
        public void setCancelMode(int cancelMode) {
            this.mCancelMode = cancelMode;
        }
    }

    /**
     * Callback of dialog click event
     *
     * @since 2021-05-25
     */
    public interface Callback {
        /**
         * "Rate now" event
         */
        void onYesClicked();

        /**
         * "No, thanks" event
         */
        void onNoClicked();

        /**
         * "Later" event
         */
        void onCancelClicked();
    }

    /*
     * 浏览器
     * */
    private static void loadUrl(Uri urlString, Context context) {
        BrowserIntents.from(context).openLink(urlString).show();
    }
}
